package integrity

import (
	"context"
	"fmt"
	"os"
	"sync"
	"time"

	"github.com/smart-snapshotter/pkg/storage"
	"github.com/smart-snapshotter/pkg/types"
)

// Manager implements the IntegrityManager interface
type Manager struct {
	config           *IntegrityConfig
	hashCalculator   *HashCalculator
	checksumCalc     *SimpleChecksumCalculator
	metadataBackend  storage.MetadataBackend
	recoveryHandler  RecoveryHandler
	verificationJobs map[string]*VerificationJob
	mu               sync.RWMutex
	closed           bool
}

// VerificationJob represents a scheduled verification job
type VerificationJob struct {
	Key      string
	Interval time.Duration
	Cancel   context.CancelFunc
	LastRun  time.Time
}

// NewManager creates a new integrity manager
func NewManager(config *IntegrityConfig, metadataBackend storage.MetadataBackend) (*Manager, error) {
	if config == nil {
		config = &IntegrityConfig{
			DefaultAlgorithm:       SHA256,
			EnableChecksum:       true,
			EnableRecovery:       false,
			VerificationInterval: 24 * time.Hour,
			MaxRecoveryAttempts:  3,
			EnableBackup:         false,
		}
	}

	if metadataBackend == nil {
		return nil, fmt.Errorf("metadata backend is required")
	}

	manager := &Manager{
		config:           config,
		hashCalculator:   NewHashCalculator(config),
		checksumCalc:   NewSimpleChecksumCalculator(),
		metadataBackend:  metadataBackend,
		verificationJobs: make(map[string]*VerificationJob),
	}

	// Initialize recovery handler if enabled
	if config.EnableRecovery {
		// For now, we'll skip recovery handler initialization
		// This can be added later when the storage backend integration is complete
		fmt.Println("Recovery handler initialization skipped - requires backup directory configuration")
	}

	return manager, nil
}

// StoreIntegrityMetadata stores integrity metadata for data
func (m *Manager) StoreIntegrityMetadata(ctx context.Context, key string, metadata *IntegrityMetadata) error {
	if m.closed {
		return fmt.Errorf("integrity manager is closed")
	}

	if metadata == nil {
		return fmt.Errorf("integrity metadata is nil")
	}

	// Validate metadata
	if err := m.validateMetadata(metadata); err != nil {
		return fmt.Errorf("invalid metadata: %w", err)
	}

	// Convert metadata to map for storage
	metadataMap := m.metadataToMap(metadata)

	if err := m.metadataBackend.StoreMetadata(ctx, key, metadataMap); err != nil {
		return fmt.Errorf("failed to store integrity metadata: %w", err)
	}

	// Schedule verification if interval is configured
	if m.config.VerificationInterval > 0 {
		if err := m.ScheduleVerification(ctx, key, m.config.VerificationInterval); err != nil {
			// Log error but don't fail the operation
			fmt.Printf("Warning: failed to schedule verification for %s: %v\n", key, err)
		}
	}

	return nil
}

// LoadIntegrityMetadata loads integrity metadata
func (m *Manager) LoadIntegrityMetadata(ctx context.Context, key string) (*IntegrityMetadata, error) {
	if m.closed {
		return nil, fmt.Errorf("integrity manager is closed")
	}

	metadataMap, err := m.metadataBackend.LoadMetadata(ctx, key)
	if err != nil {
		if err == types.ErrStorageNotFound {
			return nil, fmt.Errorf("integrity metadata not found for key %s", key)
		}
		return nil, fmt.Errorf("failed to load integrity metadata: %w", err)
	}

	metadata := m.mapToMetadata(metadataMap)
	if metadata == nil {
		return nil, fmt.Errorf("failed to parse integrity metadata")
	}

	return metadata, nil
}

// VerifyIntegrity verifies data integrity
func (m *Manager) VerifyIntegrity(ctx context.Context, key string, data []byte) error {
	if m.closed {
		return fmt.Errorf("integrity manager is closed")
	}

	// Load integrity metadata
	metadata, err := m.LoadIntegrityMetadata(ctx, key)
	if err != nil {
		return fmt.Errorf("failed to load integrity metadata: %w", err)
	}

	// Verify data size
	if int64(len(data)) != metadata.Size {
		return &IntegrityError{
			Type:    ErrDataCorrupted,
			Message: fmt.Sprintf("size mismatch: expected %d, got %d", metadata.Size, len(data)),
		}
	}

	// Verify hash
	if err := m.hashCalculator.VerifyHash(ctx, data, metadata.Hash, metadata.Algorithm); err != nil {
		// Update status to corrupted
		metadata.Status = StatusCorrupted
		metadata.LastVerifiedAt = time.Now()
		m.updateMetadataStatus(ctx, key, metadata)
		return fmt.Errorf("hash verification failed: %w", err)
	}

	// Verify checksum if enabled
	if m.config.EnableChecksum && len(metadata.Checksum) > 0 {
		if err := m.checksumCalc.VerifyChecksum(data, metadata.Checksum); err != nil {
			// Update status to corrupted
			metadata.Status = StatusCorrupted
			metadata.LastVerifiedAt = time.Now()
			m.updateMetadataStatus(ctx, key, metadata)
			return fmt.Errorf("checksum verification failed: %w", err)
		}
	}

	// Update verification status
	metadata.Status = StatusVerified
	metadata.LastVerifiedAt = time.Now()
	if err := m.updateMetadataStatus(ctx, key, metadata); err != nil {
		// Log error but don't fail the verification
		fmt.Printf("Warning: failed to update metadata status: %v\n", err)
	}

	return nil
}

// VerifyFileIntegrity verifies file integrity
func (m *Manager) VerifyFileIntegrity(ctx context.Context, key string, filePath string) error {
	if m.closed {
		return fmt.Errorf("integrity manager is closed")
	}

	// Load integrity metadata
	metadata, err := m.LoadIntegrityMetadata(ctx, key)
	if err != nil {
		return fmt.Errorf("failed to load integrity metadata: %w", err)
	}

	// Check file size first
	fileInfo, err := os.Stat(filePath)
	if err != nil {
		return fmt.Errorf("failed to stat file: %w", err)
	}

	if fileInfo.Size() != metadata.Size {
		return &IntegrityError{
			Type:    ErrDataCorrupted,
			Message: fmt.Sprintf("file size mismatch: expected %d, got %d", metadata.Size, fileInfo.Size()),
		}
	}

	// Verify file hash
	if err := m.hashCalculator.VerifyFileHash(ctx, filePath, metadata.Hash, metadata.Algorithm); err != nil {
		// Update status to corrupted
		metadata.Status = StatusCorrupted
		metadata.LastVerifiedAt = time.Now()
		m.updateMetadataStatus(ctx, key, metadata)
		return fmt.Errorf("file hash verification failed: %w", err)
	}

	// Update verification status
	metadata.Status = StatusVerified
	metadata.LastVerifiedAt = time.Now()
	if err := m.updateMetadataStatus(ctx, key, metadata); err != nil {
		// Log error but don't fail the verification
		fmt.Printf("Warning: failed to update metadata status: %v\n", err)
	}

	return nil
}

// MarkAsCorrupted marks data as corrupted
func (m *Manager) MarkAsCorrupted(ctx context.Context, key string, reason string) error {
	if m.closed {
		return fmt.Errorf("integrity manager is closed")
	}

	metadata, err := m.LoadIntegrityMetadata(ctx, key)
	if err != nil {
		return fmt.Errorf("failed to load integrity metadata: %w", err)
	}

	metadata.Status = StatusCorrupted
	metadata.LastVerifiedAt = time.Now()

	// Store corruption reason in metadata
	metadataMap := m.metadataToMap(metadata)
	metadataMap["corruption_reason"] = reason

	if err := m.metadataBackend.StoreMetadata(ctx, key, metadataMap); err != nil {
		return fmt.Errorf("failed to update corruption status: %w", err)
	}

	return nil
}

// GetCorruptedData returns list of corrupted data keys
func (m *Manager) GetCorruptedData(ctx context.Context) ([]string, error) {
	if m.closed {
		return nil, fmt.Errorf("integrity manager is closed")
	}

	// List all metadata keys (empty prefix means all keys)
	allKeys, err := m.metadataBackend.ListMetadata(ctx, "")
	if err != nil {
		return nil, fmt.Errorf("failed to list metadata keys: %w", err)
	}

	var corruptedKeys []string
	for _, key := range allKeys {
		metadata, err := m.LoadIntegrityMetadata(ctx, key)
		if err != nil {
			continue // Skip keys that can't be loaded
		}

		if metadata.Status == StatusCorrupted {
			corruptedKeys = append(corruptedKeys, key)
		}
	}

	return corruptedKeys, nil
}

// ScheduleVerification schedules periodic verification
func (m *Manager) ScheduleVerification(ctx context.Context, key string, interval time.Duration) error {
	if m.closed {
		return fmt.Errorf("integrity manager is closed")
	}

	m.mu.Lock()
	defer m.mu.Unlock()

	// Cancel existing job if present
	if existingJob, exists := m.verificationJobs[key]; exists {
		existingJob.Cancel()
		delete(m.verificationJobs, key)
	}

	// Create new verification job
	jobCtx, cancel := context.WithCancel(ctx)
	job := &VerificationJob{
		Key:      key,
		Interval: interval,
		Cancel:   cancel,
		LastRun:  time.Now(),
	}

	m.verificationJobs[key] = job

	// Start verification goroutine
	go m.runVerificationJob(jobCtx, job)

	return nil
}

// Close closes the integrity manager
func (m *Manager) Close() error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if m.closed {
		return nil
	}

	m.closed = true

	// Cancel all verification jobs
	for _, job := range m.verificationJobs {
		job.Cancel()
	}
	m.verificationJobs = make(map[string]*VerificationJob)

	return nil
}

// Helper methods

func (m *Manager) validateMetadata(metadata *IntegrityMetadata) error {
	if metadata.Hash == "" {
		return fmt.Errorf("hash is required")
	}
	if metadata.Algorithm == "" {
		return fmt.Errorf("algorithm is required")
	}
	if metadata.Size < 0 {
		return fmt.Errorf("size must be non-negative")
	}
	if metadata.Status == "" {
		metadata.Status = StatusUnknown
	}
	return nil
}

func (m *Manager) metadataToMap(metadata *IntegrityMetadata) map[string]string {
	return map[string]string{
		"hash":             metadata.Hash,
		"algorithm":        string(metadata.Algorithm),
		"checksum":         string(metadata.Checksum),
		"size":             fmt.Sprintf("%d", metadata.Size),
		"created_at":       metadata.CreatedAt.Format(time.RFC3339),
		"last_verified_at": metadata.LastVerifiedAt.Format(time.RFC3339),
		"status":           string(metadata.Status),
	}
}

func (m *Manager) mapToMetadata(metadataMap map[string]string) *IntegrityMetadata {
	if metadataMap == nil {
		return nil
	}

	metadata := &IntegrityMetadata{
		Hash:      metadataMap["hash"],
		Algorithm: HashAlgorithm(metadataMap["algorithm"]),
		Status:    VerificationStatus(metadataMap["status"]),
	}

	// Parse size
	if sizeStr := metadataMap["size"]; sizeStr != "" {
		var size int64
		if _, err := fmt.Sscanf(sizeStr, "%d", &size); err == nil {
			metadata.Size = size
		}
	}

	// Parse timestamps
	if createdAtStr := metadataMap["created_at"]; createdAtStr != "" {
		if createdAt, err := time.Parse(time.RFC3339, createdAtStr); err == nil {
			metadata.CreatedAt = createdAt
		}
	}

	if lastVerifiedAtStr := metadataMap["last_verified_at"]; lastVerifiedAtStr != "" {
		if lastVerifiedAt, err := time.Parse(time.RFC3339, lastVerifiedAtStr); err == nil {
			metadata.LastVerifiedAt = lastVerifiedAt
		}
	}

	return metadata
}

func (m *Manager) updateMetadataStatus(ctx context.Context, key string, metadata *IntegrityMetadata) error {
	metadataMap := m.metadataToMap(metadata)
	return m.metadataBackend.StoreMetadata(ctx, key, metadataMap)
}

func (m *Manager) runVerificationJob(ctx context.Context, job *VerificationJob) {
	ticker := time.NewTicker(job.Interval)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			job.LastRun = time.Now()

			// Perform verification
			// Note: This is a simplified implementation
			// In a real implementation, you would need to locate the actual data
			// and perform the verification
			fmt.Printf("Running scheduled verification for key: %s\n", job.Key)
		}
	}
}